/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.core
{
	import org.etherframework.core.events.InteractionEvent;
	import org.etherframework.core.util.ClassUtil;
	import org.etherframework.core.util.DataAccess;
	import org.etherframework.core.util.ListenerUtil;
	import org.etherframework.dependency.DependencyInfo;
	import org.etherframework.metadata.MetadataInfo;
	import org.etherframework.wire.WireInfo;
	
	import mx.core.IUIComponent;
	import mx.managers.ISystemManager;
	
	import flash.system.ApplicationDomain;
	import flash.events.Event;
	import flash.events.EventPhase;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	import flash.display.DisplayObjectContainer;
	import flash.display.DisplayObject;
	
	[ExcludeClass]
	internal final class Bridge implements IBridge
	{
		private var info:ControllerInfo;
		
		private var listeners:ListenerUtil;
		
		public function Bridge(info:ControllerInfo)
		{
			this.info = info;
			
			info.controller.addEventListener("register", registerHandler, false, 200);
			info.controller.addEventListener("unregister", unregisterHandler, false, 200);
			_util = new ClassUtil(new Dictionary());
			_shared = new DataAccess(info.sharedData);
			_local = new DataAccess(info.localData);
			listeners = new ListenerUtil(_local.addItem("__$events", new Dictionary()));
			
			var str:String = String(info.host);
			_token = (str.indexOf(".") > 0) ? (str.substr(str.lastIndexOf(".") + 1) + info.token) : info.token;
		}		
		
		public function get systemManager():ISystemManager
		{
			return info.systemManager;
		}
		
		public function get host():IUIComponent
		{
			return info.host;
		}
		
		public function get domain():ApplicationDomain
		{
			return info.domain as ApplicationDomain;
		}
		
		public function getMetadata(value:Object):MetadataInfo
		{
			return info.controller.metadata.getItem(value) as MetadataInfo;
		}
		
		public function addMetadata(value:Object):MetadataInfo
		{
			return info.controller.metadata.addItem(value) as MetadataInfo;
		}
		
		public function removeMetadata(value:Object):MetadataInfo
		{
			return info.controller.metadata.removeItem(value) as MetadataInfo;
		}
		
		public function getBean(value:Object):DependencyInfo
		{
			return info.controller.dependency.getItem(value) as DependencyInfo;
		}
		
		public function addBean(value:Object):DependencyInfo
		{
			return info.controller.dependency.addItem(value) as DependencyInfo;
		}
		
		public function removeBean(value:Object):DependencyInfo
		{
			return info.controller.dependency.removeItem(value) as DependencyInfo;
		}
		
		public function getWire(value:Object):WireInfo
		{
			return info.controller.wire.getItem(value) as WireInfo;
		}
		
		public function addWire(value:Object):WireInfo
		{
			return info.controller.wire.addItem(value) as WireInfo;
		}
		
		public function removeWire(value:Object):WireInfo
		{
			return info.controller.wire.removeItem(value) as WireInfo;
		}
		
		public function dispatch(event:Event, access:String = "local", domain:String = "single"):Boolean
		{
			var dispatcher:IEventDispatcher;
			var policy:BridgePolicy = new BridgePolicy(access, domain, info.domain);
			var eventInfo:BridgeEventInfo = new BridgeEventInfo(event, policy, this);
			var evt:BridgeEvent;
			
			if (event is InteractionEvent)
				event["owner"] = _token;
			
			if ((access == Access.GLOBAL) && (domain == DomainAccess.MULTIPLE))
			{
				evt = new BridgeEvent(event.type, false, info.masterToken, eventInfo);
				getDispatcher(access, DomainAccess.SINGLE).dispatchEvent(evt);
				if (!event.isDefaultPrevented())
					getDispatcher(access, domain).dispatchEvent(evt.clone());
			}
			else if (access == Access.HIERARCHICAL)
			{
				policy = new BridgePolicy(Access._HIERARCHICAL, domain, info.domain);
				eventInfo = new BridgeEventInfo(event, policy, this);
				evt = new BridgeEvent(event.type, true, info.masterToken, eventInfo);
				getDispatcher(Access.LOCAL, domain).dispatchEvent(evt);
				if (!event.isDefaultPrevented())
				{
					policy = new BridgePolicy(Access.HIERARCHICAL_, domain, info.domain);
					eventInfo = new BridgeEventInfo(event, policy, this);
					evt = new BridgeEvent(event.type + Access.HIERARCHICAL, false, 
											info.masterToken, eventInfo);
					walkDispatch(getDispatcher(Access.LOCAL, domain) as DisplayObjectContainer, evt);
				}
			}
			else if (access == Access.HIERARCHICAL_)
			{
				evt = new BridgeEvent(event.type, false, info.masterToken, eventInfo);
				getDispatcher(Access.LOCAL, domain).dispatchEvent(evt);
				if (!event.isDefaultPrevented())
				{
					evt = new BridgeEvent(event.type + Access.HIERARCHICAL, false, 
											info.masterToken, eventInfo);
					walkDispatch(getDispatcher(Access.LOCAL, domain)  as DisplayObjectContainer, evt);
				}
			}
			else
			{
				evt = new BridgeEvent(event.type, (access == Access._HIERARCHICAL), 
													info.masterToken, eventInfo);
				getDispatcher(access, domain).dispatchEvent(evt);
			}
			
			return true;
		}
		
		public function listen(type:String, listener:Function, access:String = "local", domain:String = "single", 
							   useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
		{
			var dispatcher:IEventDispatcher;
			
			if (access == Access.HIERARCHICAL)
			{
				dispatcher = getDispatcher(Access.LOCAL, domain);
				addListener(type, dispatcher, listener, Access.HIERARCHICAL_, 
							domain, useCapture, priority);
				
				addListener(type + Access.HIERARCHICAL, dispatcher, listener, 
					Access._HIERARCHICAL, domain, useCapture, priority);
			}
			else if (access == Access.HIERARCHICAL_)
			{
				dispatcher = getDispatcher(Access.LOCAL, domain);
				addListener(type, dispatcher, listener, 
					access, domain, useCapture, priority);
			}
			else if (access == Access._HIERARCHICAL)
			{
				dispatcher = getDispatcher(Access.LOCAL, domain);
				addListener(type + Access.HIERARCHICAL, dispatcher, listener, 
					access, domain, useCapture, priority);
				
				addListener(type, dispatcher, listener, 
					Access.LOCAL, domain, useCapture, priority);
			}
			else if ((access == Access.GLOBAL) && (domain == DomainAccess.MULTIPLE))
			{
				dispatcher = getDispatcher(access, DomainAccess.SINGLE);
				addListener(type, dispatcher, listener, access, DomainAccess.SINGLE, 
							useCapture, priority);
				dispatcher = getDispatcher(access, domain);
				addListener(type, dispatcher, listener, access, domain, 
							useCapture, priority);
			}
			else
			{
				dispatcher = getDispatcher(access, domain);
				addListener(type, dispatcher, listener, access, domain, 
							useCapture, priority);
			}
		}
		
		public function unlisten(type:String, listener:Function, access:String = "local", 
								 domain:String = "single", useCapture:Boolean = false):void
		{
			if (access == Access.HIERARCHICAL)
			{
				listeners.removeListener(type, getDispatcher(Access.LOCAL, domain), 
										listener, useCapture);
				listeners.removeListener(type + Access.HIERARCHICAL,
						getDispatcher(Access.LOCAL, domain), listener, useCapture);
			}
			else if (access == Access.HIERARCHICAL_)
			{
				listeners.removeListener(type + Access.HIERARCHICAL, 
					getDispatcher(Access.LOCAL, domain), listener, useCapture);
			}
			else if (access == Access._HIERARCHICAL)
			{
				listeners.removeListener(type + Access.HIERARCHICAL, 
					getDispatcher(Access.LOCAL, domain), listener, useCapture);
				
				listeners.removeListener(type, getDispatcher(Access.LOCAL, domain), 
										listener, useCapture);
			}
			else if ((access == Access.GLOBAL) && (domain == DomainAccess.MULTIPLE))
			{
				listeners.removeListener(type, getDispatcher(access, DomainAccess.SINGLE), 
										listener, useCapture);
				listeners.removeListener(type, getDispatcher(access, domain), listener, useCapture);
			}
			else
			{
				listeners.removeListener(type, getDispatcher(access, domain), listener, useCapture);
			}
		}
		
		private var _shared:DataAccess;
		
		public function get shared():DataAccess
		{
			return _shared;
		}
		
		private var _local:DataAccess;
		
		public function get local():DataAccess
		{
			return _local;
		}
		
		private var _util:ClassUtil;
		
		public function get util():ClassUtil
		{
			return _util;
		}
		
		private var _token:String;
		
		public function get token():String
		{
			return _token;
		}
		
		private function destroy():void
		{
			info.controller.removeEventListener("register", registerHandler);
			info.controller.removeEventListener("unregister", unregisterHandler);
			
			listeners.removeListeners();
			
			_local.removeItem("__$events");
			_local = null;
			_util = null;
			_shared = null;
		}
		
		private function addListener(type:String, dispatcher:IEventDispatcher, listener:Function, 
									 access:String, domain:String, useCapture:Boolean, priority:int):void
		{
			if (!listeners.hasListener(type, dispatcher, listener))
			{
				var handler:Function = function(event:Event):void
				{
					eventHandler(event, listener, access, domain);
				};
				listeners.addListener(type, dispatcher, handler, useCapture, priority);
			}
		}
		
		private function walkDispatch(item:DisplayObjectContainer, event:BridgeEvent):void
		{
			item.dispatchEvent(event.clone());
			
			var max:int = item.numChildren;
			var child:DisplayObjectContainer;
			
			for (var i:int = 0; i < max; ++i)
			{
				child = item.getChildAt(i) as DisplayObjectContainer;
				
				if (child.numChildren)
				{
					walkDispatch(child, event);
				}
				else
				{
					child.dispatchEvent(event.clone());
				}
			}
		}
		
		//FIXME:
		private function getDispatcher(access:String, domain:String):IEventDispatcher
		{
			if (access == Access.GLOBAL)
			{
				return (domain == DomainAccess.MULTIPLE) ? 
					info.masterDispatcher : info.globalDispatcher;
			}
			
			return info.localDispatcher;
		}
		
		private function eventHandler(event:Object, handler:Function, access:String, domain:String):void
		{
			var eventInfo:Object = event.getInfo(info.masterToken);
			if (!eventInfo)
				return;
			
			if ((access == Access.LOCAL) && (event.eventPhase == 3))
				return;
				
			var policy:Object = eventInfo.policy;
			if (eventInfo.event.isDefaultPrevented())
			{
				eventInfo.event.stopImmediatePropagation();
				return;
			}
			
			// FIXME:
			if (((domain == DomainAccess.SINGLE) && (policy.domain == info.domain)) ||
				((domain == DomainAccess.MULTIPLE) && (policy.domainType == DomainAccess.MULTIPLE)) ||
				((domain == DomainAccess.MULTIPLE) && (policy.domainType == DomainAccess.SINGLE)) && 
				(policy.domain == info.domain))
			{
				if ((eventInfo.bridge == this) ||
					((access == Access._HIERARCHICAL) && (policy.accessType == Access.HIERARCHICAL_) ||
					((access == Access.HIERARCHICAL_) && (policy.accessType == Access._HIERARCHICAL)) ||
					((access == Access.GLOBAL) && (policy.accessType == access))))
				{
					if ("participants" in eventInfo.event)
						eventInfo.event["participants"].push(_token);
					
					handler(eventInfo.event);
					
					if (eventInfo.event.isDefaultPrevented())
					{
						event.stopImmediatePropagation();
					}
				}
			}
		}
		
		private function registerHandler(event:Event):void
		{
			var evt:InteractionEvent = new InteractionEvent(InteractionEvent.CONTROLLER_ADDED + _token);
			dispatch(evt, Access.GLOBAL, DomainAccess.MULTIPLE);
			
			evt = new InteractionEvent(InteractionEvent.CONTROLLER_ADDED);
			dispatch(evt, Access.GLOBAL, DomainAccess.MULTIPLE);
		}
		
		private function unregisterHandler(event:Event):void
		{
			var evt:InteractionEvent = new InteractionEvent(InteractionEvent.CONTROLLER_REMOVED + _token);
			dispatch(evt, Access.GLOBAL, DomainAccess.MULTIPLE);
			
			evt= new InteractionEvent(InteractionEvent.CONTROLLER_REMOVED);
			dispatch(evt, Access.GLOBAL, DomainAccess.MULTIPLE);
			
			destroy();
		}
		
	}
}

import flash.events.Event;

final class BridgeEvent extends Event
{
	private var token:String;
	
	private var info:Object;
	
	public function getInfo(token:String):Object
	{
		return (this.token == token) ? info : null;
	}
	
	function BridgeEvent(type:String, bubbles:Boolean, token:String, info:BridgeEventInfo)
	{
		super(type, bubbles, false);
		this.token = token;
		this.info = info;
	}
	
	override public function clone():Event
	{
		return new BridgeEvent(type, bubbles, token, info as BridgeEventInfo);
	}
}

import org.etherframework.core.IBridge;

final class BridgeEventInfo
{
	public var event:Event;
	
	public var policy:BridgePolicy;
	
	public var bridge:IBridge;
	
	function BridgeEventInfo(event:Event, policy:BridgePolicy, bridge:IBridge)
	{
		this.event = event;
		this.policy = policy;
		this.bridge = bridge;
	}
}

final class BridgePolicy
{
	public var accessType:String;
	
	public var domainType:String;
	
	public var domain:Object;
	
	function BridgePolicy(accessType:String, domainType:String, domain:Object)
	{
		this.accessType = accessType;
		this.domainType = domainType;
		this.domain = domain;
	}
}